package com.eltech.battleofrunes;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import android.text.Layout;
import android.text.method.ScrollingMovementMethod;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class MainActivity extends Activity {
	
	//==========================================================================
	//
	// Различные игровые константы
	//
	//==========================================================================
	// Константа запроса на включение блютуса. Устроено это таким образом:
	// создается Intent на включение блютус, пользователь нажимает да или нет.
	// Нам в функции onActivityResult приходит сообщение о решении
	// пользователя. Так как в эту функцию валятся результаты всех таких
	// запросов, различают их по requestCode. Вот эта константа для этого
	// и нужна.
	public static final int REQUEST_ENABLE_BT = 1;
	
	// Это типы сообщений, которыми обмениваются дочерние потоки, отвечающие
	// за соединение по блютусу. Проблема в том, что эти дочерние потоки
	// не имеют прямого доступа к UI приложения. Менять UI (например писать
	// в лог) можно только из основного потока (main activity). Поэтому,
	// чтобы основной поток узнал новых данных, пришедших по сокету, поток,
	// отвечающий за считывание этих данных, должен послать ему сообщение.
	// Вообще же это удобно и повышает структурированность кода.
	public static final int WRITE_LOG_MSG = 1;
	public static final int SOCKET_CONNECTED = 2;
	public static final int MESSAGE_READ = 3;
	public static final int SOCKET_CLOSED = 4;
	// Размер входного буфера для приема сообщений
	public static final int RECEIVE_BUFFER_SIZE = 128;
	
	// Числовые id рун
	public static final int RUNE_ANSUZ = 1;
	public static final int RUNE_BERKANO = 2;
	public static final int RUNE_DAGAZ = 3;
	public static final int RUNE_ELHAZ = 4;
	public static final int RUNE_EVAZ = 5;
	public static final int RUNE_EYVAZ = 6;
	public static final int RUNE_FEHU = 7;
	public static final int RUNE_GEBO = 8;
	public static final int RUNE_HAGALAZ = 9;
	public static final int RUNE_INGVAZ = 10;
	public static final int RUNE_ISE = 11;
	public static final int RUNE_JERA = 12;
	public static final int RUNE_KENAZ = 13;
	public static final int RUNE_LAGUZ = 14;
	public static final int RUNE_MANAZ = 15;
	public static final int RUNE_NAUTIZ = 16;
	public static final int RUNE_OFALA = 17;
	public static final int RUNE_PERTRO = 18;
	public static final int RUNE_RAIDO = 19;
	public static final int RUNE_SOVILO = 20;
	public static final int RUNE_TIVAZ = 21;
	public static final int RUNE_TURISAZ = 22;
	public static final int RUNE_URUZ = 23;
	public static final int RUNE_VUNJO = 24;
	
	// И тут же хэши для удобства обращения id в строки и наоборот.
	// String To Int Runes
	private HashMap<String, Integer> stiRunes = new HashMap<String, Integer>();
	// Int To String Runes
	private HashMap<Integer, String> itsRunes = new HashMap<Integer, String>();
	// Int to Bitmap Runes
	private HashMap<Integer, Integer> itbRunes = new HashMap<Integer, Integer>();
	
	// Константы протокола общения. Сообщение формируется следующим образом:
	// [2 байта длины][2 байта id сообщения][.. n байт параметров ..]
	// Пример (один игрок отправляет другому свои руны перед боем):
	// [00 06] [00 01] [03 01 02 03]
	// где
	// [00 06] - длина тела сообщения 6 байт (поле длины не учитывается)
	// [00 01] - id сообщения MSG_LOOK_AT_MY_RUNES
	// [03     - количество передаваемых рун
	//  01 02 03] - id рун
	public static final short MSG_LOOK_AT_MY_RUNES = 1;
	public static final short MSG_ACCEPT_OPPONENT_RUNES = 2;
	public static final short MSG_DECLINE_OPPONENT_RUNES = 3;
	public static final short MSG_YOUR_TURN_STARTS = 4;
	public static final short MSG_CAST_SPELL = 5;
	public static final short MSG_I_AM_DEAD = 6;
	public static final short MSG_GOODBYE = 7;
	
	//==========================================================================
	//
	// Переменные для работы с bluetooth
	//
	//==========================================================================
	// Это уникальных идентификатор приложения. Сгенерён в интернете.
	// Нужен для соединения по блютусу. С его помощью наше приложение
	// на разных телефонах сможет различать сообщения, направленные ему
	// через общую среду передачи данных (привет Яновскому).
	public static final UUID APP_UUID = UUID.fromString("c3ed7bf4-c51d-4f68-ab96-7e1c202dea7d");
	// Имя приложения, тоже нужно для блютуса. Выступает в роли имени
	// сервиса, но это не так важно.
	public static final String APP_NAME = "Battle of Runes";
	
	// Волшебный класс работы с блютусом. Через него устанавливается соединение.
	public BluetoothAdapter mBluetoothAdapter = null;
	
	// Массив имен, которые будут выводиться в ListView. Это собственно имена
	// блютус устройств сопряженных или найденных поблизости.
	private ArrayAdapter<String> opponentsStringArrayAdapter = null;
	// Массив соответствующих девайсов.
	private ArrayAdapter<BluetoothDevice> opponentsArrayAdapter = null;
	// Хэш для отслеживания уникальности устройств, добавляемых в массив.
	private HashSet<String> opponentsMACs = new HashSet<String>();
	
	//==========================================================================
	//
	// Различные игровые переменные
	//
	//==========================================================================
	// Список рун игрока
	private ArrayList<String> runes = new ArrayList<String>();
	// Массивы для вывода рун в списки в экранах инвентаря и выбора рун
	// перед боем.
	private ArrayAdapter<String> runesInInventory = null;
	private ArrayAdapter<String> runesInBattle = null;
	//private ArrayAdapter<Drawable> runesInBattle = null;
	// Максимальное количество рун в бою
	private int maxRunesInBattle = 3;
	// Готовность игроков
	private boolean isMeReady = false;
	private boolean isOppReady = false;
	// Клиент или сервер?
	private boolean isServer = false;
	public static final int IS_SERVER = 1; 
	public static final int IS_CLIENT = 2; 
	
	//==========================================================================
	//
	// Потоки, отвечающие за общение.
	//
	//==========================================================================
	// Потоки, отвечающие за общение. Приложение изначально (после включения
	// блютуса) открывает слушающий сокет, то есть прикидывается сервером.
	// За это отвечает AcceptThread.
	// Но также можно и поискать устройства, и попробовать подключиться
	// к другому экземпляру приложения на другом устройстве. За это отвечает
	// ConnectThread.
	// Эти два потока занимаются исключительно установлением соединения.
	private ConnectThread connectThread = null;
	private AcceptThread acceptThread = null;
	// Вот этот поток уже после установления соединения занимается собственно
	// передачей данных.
	private ConnectedThread connectedThread = null;
	private boolean connectionEstablished = false;
	
	// Это хендлер, с помощью которого будут слаться сообщения в главный
	// поток приложения.
	public final Handler mHandler = new MyHandler();
	// А вот и его описание. Получили, от кого-нибудь сообщение - сделали
	// что-нибудь соответствующее.
	private class MyHandler extends Handler {
		@Override
		public void handleMessage (Message msg) {
			if (msg.what == WRITE_LOG_MSG) {
				log(msg.obj.toString());
			}
			else if (msg.what == SOCKET_CONNECTED) {
				connectedThread = new ConnectedThread((BluetoothSocket)(msg.obj));
				connectedThread.start();
				connectionEstablished = true;
				isServer = msg.arg1 == IS_SERVER;
				if (isServer) {
					acceptThread = null;
				}
				else {
					connectThread = null;
				}
				showScreen(chooseRunesScreen);
			}
			else if (msg.what == MESSAGE_READ) {
				int nbytes = msg.arg1;
				byte[] buffer = (byte[]) msg.obj;
				msgBuf.put(buffer, 0, nbytes);
				bytesAvailable += nbytes;
				while (bytesAvailable > 0) {
					if (lastMsgSize == 0) {
						if (bytesAvailable >= 2)
							lastMsgSize = socketGetShort();
						else
							break;
						if (bytesAvailable >= lastMsgSize)
							parseMessage();
					}
					else if (bytesAvailable >= lastMsgSize)
						parseMessage();
					else
						break;
				}
			}
			else if (msg.what == SOCKET_CLOSED) {
				timerHandler.removeCallbacks(timerRunnable);
				if (connectionEstablished && connectedThread != null) {
					connectedThread.cancel();
					connectedThread = null;
					alert("Other player is gone.");
					connectionEstablished = false;
					startAcceptThread();
				}
				/// TODO: Добавить вывод сообщения
				clearStateAfterBattle();
				showScreen(welcomeScreen);
			}
		}
	}
	
	//==========================================================================
	//
	// Обработка входящих сообщений от оппонента.
	//
	//==========================================================================
	
	private void shrinkByteBuffer (ByteBuffer bb, int count, int newPos) {
		bb.position(0);
		bb.put(bb.array(), count, bb.limit() - count);
		bb.position(newPos);
	}
	
	private short socketGetShort () {
		short ret = msgBuf.getShort(0);
		bytesAvailable -= 2;
		shrinkByteBuffer (msgBuf, 2, bytesAvailable);
		return ret;
	}
	
	private byte socketGetByte () {
		byte ret = msgBuf.get(0);
		bytesAvailable -= 1;
		shrinkByteBuffer (msgBuf, 1, bytesAvailable);
		return ret;
	}
	
	private float socketGetFloat () {
		float ret = msgBuf.getFloat(0);
		bytesAvailable -= 4;
		shrinkByteBuffer (msgBuf, 4, bytesAvailable);
		return ret;
	}
	
	// Буфер, куда будут складываться байты, пришедшие по сокету.
	// Так как в блютусе используется TCP, то приходится так изворачиваться
	// и самому отделять сообщения друг от друга.
	private ByteBuffer msgBuf = ByteBuffer.allocate(RECEIVE_BUFFER_SIZE);
	private int lastMsgSize = 0;
	private int bytesAvailable = 0;
	// Функция парсинга пришедших от оппонента сообщений
	private void parseMessage () {
		short msgId = socketGetShort();
		switch (msgId) {
			case MSG_LOOK_AT_MY_RUNES: lookAtMyRunesHandler(); break;
			case MSG_ACCEPT_OPPONENT_RUNES: acceptOpponentHandler(); break;
			case MSG_DECLINE_OPPONENT_RUNES: declineOpponentHandler(); break;
			case MSG_YOUR_TURN_STARTS: yourTurnStartsHandler(); break;
			case MSG_CAST_SPELL: castSpellHandler(); break;
			case MSG_I_AM_DEAD: iAmDeadHandler(); break;
			case MSG_GOODBYE: goodbyeHandler(); break;
		}
		lastMsgSize = 0;
	}

	private void lookAtMyRunesHandler () {
		byte count = socketGetByte();
		byte[] runes = new byte[count];
		String msg = "Opponent suggests next runes:\n\n";
		for (int i = 0; i < count; ++i) {
			runes[i] = socketGetByte();
			msg += itsRunes.get(Integer.valueOf(runes[i])) + "\n";
		}
		msg += "\nDo you accept?";
		acceptRunesText.setText(msg);
		showScreen(acceptRunesScreen);
	}
	
	private void acceptOpponentHandler () {
		isMeReady = true;
		if (isOppReady)
			startBattle();
	}

	private void declineOpponentHandler () {
		isMeReady = false;
		readyToBattleButton.setEnabled(true);
	}
	
	private void yourTurnStartsHandler() {
		isMyTurnNow = true;
		secondsTillEndOfTurn = ONE_ROUND_TIME;
		updateStats();
		setEnabledBattleScreen(true);
		timerHandler.postDelayed(timerRunnable, 1000);
	}
	
	private void castSpellHandler() {
		// TODO Auto-generated method stub
		short runeId = socketGetShort();
		float multiplier = socketGetFloat();
		//if (runeId == RUNE_ANSUZ) {
			currentHitPoints -= Math.round(10 * (double) multiplier);//3 + Math.round(5 * Math.random());
			if (Math.random() < 0.5) 
				blog("URGHH! That's hurt!");
			else
				blog("Damn it!");
			if (currentHitPoints <= 0) {
				endBattle(false);
			}
		//}
		//else if (runeId == RUNE_BERKANO) {
			
		//}
		//else if (runeId == RUNE_ELHAZ) {
			
		//}
		updateStats();
	}
	
	private void iAmDeadHandler() {
		// Противник мертв
		resultsLog.setText("You are WINNER!\nWant more?");
		timerHandler.removeCallbacks(timerRunnable);
		showScreen(resultsScreen);
	}
	
	private void goodbyeHandler() {
		// TODO Auto-generated method stub
		
	}
	
	//==========================================================================
	//
	// Вещи относящиеся к бою, таймер и все такое.
	//
	//==========================================================================
	// Время одного хода в секундах
	public static final int ONE_ROUND_TIME = 10;
	// Жизни всего и текущее значение
	public static final int TOTAL_HIT_POINTS = 20;
	private int currentHitPoints = 0;
	// Чей сейчас ход
	private boolean isMyTurnNow = false;
	// Секунд до конца хода
	private int secondsTillEndOfTurn = 0;
	// Выбранная руна
	private int choosenRuneId = 0;
	//-------------------------------------------
	// How to start timer
	// timerHandler.postDelayed(timerRunnable, 0);
	// How to stop timer
	// timerHandler.removeCallbacks(timerRunnable);
	//-------------------------------------------
	private Handler timerHandler = new Handler();
	Runnable timerRunnable = new Runnable() {
        @Override
        public void run() {
        	secondsTillEndOfTurn -= 1;
        	if (isMyTurnNow)
        		updateStats();
        	if (isServer) { // Сервер
	        	if (secondsTillEndOfTurn == 0) {
	        		if (isMyTurnNow) {
	        			setEnabledBattleScreen(false);
	        			sendOpponentTurnStarts();
	        		}
	        		else {
	        			setEnabledBattleScreen(true);
	        		}
	        		isMyTurnNow = !isMyTurnNow;
	        		secondsTillEndOfTurn = ONE_ROUND_TIME;
	        		if (isMyTurnNow)
	        			updateStats();
	        	}
	        	timerHandler.postDelayed(this, 1000);
        	}
        	else { // Клиент
        		if (secondsTillEndOfTurn == 0) {
	        		setEnabledBattleScreen(false);
	        		isMyTurnNow = !isMyTurnNow;
	        		timerHandler.removeCallbacks(timerRunnable);
	        	}
        		else
        			timerHandler.postDelayed(this, 1000);
        	}
        }
    };
    
    private void sendOpponentTurnStarts () {
    	ByteBuffer bb = ByteBuffer.allocate(4);
		bb.putShort((short) 2);
		bb.putShort(MSG_YOUR_TURN_STARTS);
		connectedThread.write(bb.array());
    }
    
    private void clearStateAfterBattle () {
    	readyToBattleButton.setEnabled(true);
    	isOppReady = isMeReady = false;
    	opponentReadyText.setText("Opp not ready");
    }
	
	// Функция запускающая бой.
	private void startBattle () {
		log("Battle started");
		// Сперва выключаем экран боя
		setEnabledBattleScreen(false);
		// Загружаем выбранные руны в список.
		inflateRunesToBattle();
		// Показываем экран
		showScreen(battleScreen);
		// Дальше решаем (если мы клиент, то просто сидим и ждем).
		if (isServer) {
			// Если мы сервер, то решаем кто ходит первым
			isMyTurnNow = Math.random() < 0.5;
			if (isMyTurnNow) {
				setEnabledBattleScreen(true);
			}
			else {
				sendOpponentTurnStarts();
			}
			secondsTillEndOfTurn = ONE_ROUND_TIME;
			// Запускаем таймер хода
			timerHandler.postDelayed(timerRunnable, 1000);
		}
		currentHitPoints = TOTAL_HIT_POINTS;
		updateStats();
	}
	
	private void endBattle (boolean winner) {
		ByteBuffer bb = ByteBuffer.allocate(4);
		bb.putShort((short) 2);
		bb.putShort(MSG_I_AM_DEAD);
		connectedThread.write(bb.array());
		
		resultsLog.setText("You are LOSER!\nWant more?");
		timerHandler.removeCallbacks(timerRunnable);
		showScreen(resultsScreen);
	}
	
	// Функция выводит информацию в окно статов.
	private void updateStats () {
		statsText.setText("TIME: " + String.valueOf(secondsTillEndOfTurn) +
				"\nHEALTH: " + String.valueOf(currentHitPoints));
	}
	
	// Функция вкючает или выключает реакцию кнопок в экране боя
	// на действия пользователя, чтобы в не свой ход не жали всякое.
	private void setEnabledBattleScreen (boolean enabled) {
		runesChooser.setEnabled(enabled);
		int len = runesChooser.getChildCount();
		for (int i = 0; i < len; ++i) {
			View child = runesChooser.getChildAt(i);
			child.setEnabled(enabled);
			child.setClickable(enabled);
		}
		castButton.setEnabled(enabled);
	}
	
	private void inflateRunesToBattle () {
		int len = runesChooser.getChildCount();
		for (int i = 0; i < len; ++i) {
			View child = runesChooser.getChildAt(i);
			child.setOnClickListener(null);
		}
		runesChooser.removeAllViews();
		len = runesInBattle.getCount();
		//len = runes.size();
		for (int i = 0; i < len; ++i) {
			int id = stiRunes.get(runesInBattle.getItem(i));
			//int id = stiRunes.get(runes.get(i));
			ImageView imgView = new ImageView(this, null, id);
			imgView.setImageResource(itbRunes.get(id));
			imgView.setOnClickListener(runeInBattleClickHandler);
			imgView.setId(id);
			runesChooser.addView(imgView);
		}
	}
	
	// TODO: Set choosenRuneId
	private class RuneInBattleClickHandler implements View.OnClickListener {
		public void onClick(View v) {
            ImageView img = (ImageView) v;
            int id = img.getId();
            //currentRune.setImageResource(itbRunes.get(id));
            choosenRuneId = id;
            blog("Rune №" + String.valueOf(choosenRuneId) + " (" +
            		itsRunes.get(choosenRuneId) + ")");
		}
	}
	private View.OnClickListener runeInBattleClickHandler = new RuneInBattleClickHandler();
	
	//==========================================================================
	//
	// Эта штука для того чтобы лог вызывать по двойному тапу.
	//
	//==========================================================================
	private GestureDetector gestureDetector;
	private class GestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }
        // event when double tap occurs
        @Override
        public boolean onDoubleTap(MotionEvent e) {
            if (logScreen.getVisibility() == View.GONE)
            	showScreen(logScreen);
            else
            	showScreen(lastViewedScreen);
            return true;
        }
    }
	@Override
    public boolean onTouchEvent(MotionEvent e) {
        return gestureDetector.onTouchEvent(e);
    }
	
	//==========================================================================
	//
	// Ссылки на элементы управления (разбиты на группы по смыслу,
	// эти ссылки будут инициализированы в функции initViewsLinks,
	// чтобы в дальнейшем не нужно было всякий раз для обращения
	// к какому-либо элементу управления вызывать функцию findViewById).
	// Ссылки будут создаваться по мере необходимости.
	//
	//==========================================================================
	private View lastViewedScreen = null;
	private ArrayList<View> allScreens = new ArrayList<View>();
	// Log screen
	private RelativeLayout logScreen;
	private TextView logTextView;
	// Welcome screen
	private LinearLayout welcomeScreen;
	private Button startGameButton;
	private Button showInventoryButton;
	private Button exitAppButton;
	// Find opponent screen
	private RelativeLayout findOpponentScreen;
	private Button findOpponentButton;
	private Button findMoreOpponentsButton;
	private ListView opponentsList;
	// Choose runes screen
	private RelativeLayout chooseRunesScreen;
	private ListView chooseRunesInvList;
	private ListView chooseRunesBatList;
	private TextView opponentReadyText;
	private Button readyToBattleButton;
	// Inventory screen
	private RelativeLayout inventoryScreen;
	private ListView inventoryList;
	// Accept runes screen
	private RelativeLayout acceptRunesScreen;
	private TextView acceptRunesText;
	// Battle screen
	private LinearLayout battleScreen;
	private LinearLayout runesChooser;
	private org.sprite2d.apps.pp.PainterCanvas canvas;
	//private ImageView currentRune;
	private Button castButton;
	private TextView statsText;
	private TextView battleLog;
	// Results screen
	private RelativeLayout resultsScreen;
	private TextView resultsLog;
	
	// Функция инициализирует ссылки на элементы управления.
	// Вызывается из функции onCreate.
	private void initViewsLinks () {
		// Log screen
		logScreen = (RelativeLayout) findViewById(R.id.logScreen);
		logTextView = (TextView) findViewById(R.id.logTextView);
		// Welcome screen
		welcomeScreen = (LinearLayout) findViewById(R.id.welcomeScreen);
		startGameButton = (Button) findViewById(R.id.startGameButton);
		showInventoryButton = (Button) findViewById(R.id.showInventoryButton);
		exitAppButton = (Button) findViewById(R.id.exitAppButton);
		// Find opponent screen
		findOpponentScreen = (RelativeLayout) findViewById(R.id.findOpponentScreen);
		findOpponentButton = (Button) findViewById(R.id.findOpponentButton);
		findMoreOpponentsButton = (Button) findViewById(R.id.findMoreOpponentsButton);
		opponentsList = (ListView) findViewById(R.id.opponentsList);
		// Choose runes screen
		chooseRunesScreen = (RelativeLayout) findViewById(R.id.chooseRunesScreen);
		chooseRunesInvList = (ListView) findViewById(R.id.chooseRunesInvList);
		chooseRunesBatList = (ListView) findViewById(R.id.chooseRunesBatList);
		opponentReadyText = (TextView) findViewById(R.id.opponentReadyText);
		readyToBattleButton = (Button) findViewById(R.id.readyToBattleButton);
		// Inventory screen
		inventoryScreen = (RelativeLayout) findViewById(R.id.inventoryScreen);
		inventoryList = (ListView) findViewById(R.id.inventoryList);
		// Accept runes screen
		acceptRunesScreen = (RelativeLayout) findViewById(R.id.acceptRunesScreen);
		acceptRunesText = (TextView) findViewById(R.id.acceptRunesText);
		// Battle screen
		battleScreen = (LinearLayout) findViewById(R.id.battleScreen);
		runesChooser = (LinearLayout) findViewById(R.id.runesChooser);
		canvas = (org.sprite2d.apps.pp.PainterCanvas) findViewById(R.id.canvas);
		//currentRune = (ImageView) findViewById(R.id.currentRune);
		castButton = (Button) findViewById(R.id.castButton);
		statsText = (TextView) findViewById(R.id.statsText);
		battleLog = (TextView) findViewById(R.id.battleLog);
		// Results screen
		resultsScreen = (RelativeLayout) findViewById(R.id.resultsScreen);
		resultsLog = (TextView) findViewById(R.id.resultsLog);
		
		// Добавляем экраны в массив
		allScreens.add(logScreen);
		allScreens.add(welcomeScreen);
		allScreens.add(findOpponentScreen);
		allScreens.add(chooseRunesScreen);
		allScreens.add(inventoryScreen);
		allScreens.add(acceptRunesScreen);
		allScreens.add(battleScreen);
		allScreens.add(resultsScreen);
	}
	
	// Функция прячет все экраны
	private void hideAllScreens () {
		for (View v : allScreens) { v.setVisibility(View.GONE); }
	}
	
	// Функция прячет лог
	public void hideLog (View view) {
		showScreen(lastViewedScreen);
	}
	
	// Функция показывает заданный экран
	private void showScreen (View screen) {
		hideAllScreens();
		screen.setVisibility(View.VISIBLE);
		if (!screen.equals(logScreen))
			lastViewedScreen = screen;
	}
	
	//==========================================================================
	//
	// Обработчики кнопок.
	//
	//==========================================================================
	// Функция выводит в ListView список сопряженных устройств.
	// После этого появляется кнопка, позволяющая запустить поиск устройств
	// поблизости.
	public void findOpponentButtonHandler(View view) {
	     log("findOpponentButtonHandler");
	     Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
		 // If there are paired devices
		 if (pairedDevices.size() > 0) {
		     // Loop through paired devices
		     for (BluetoothDevice device : pairedDevices) {
		    	 if (opponentsMACs.add(device.getAddress())) {
		    		 // Add the name and address to an array adapter to show in a ListView
		    		 opponentsStringArrayAdapter.add(device.getName() + "\n" + device.getAddress());
		    		 opponentsArrayAdapter.add(device);
		    	 }
		     }
		 }
		 //final ListView opponentsList = (ListView) findViewById(R.id.opponentsList);
		 opponentsList.setAdapter(opponentsStringArrayAdapter);
		 // Show second button for discovery option
		 findMoreOpponentsButton.setVisibility(View.VISIBLE);
	}
	
	// Ну вот так просто и запускаем поиск устройств.
	public void findMoreOpponentsButtonHandler(View view) {
	     log("findMoreOpponentsButtonHandler");
	     mBluetoothAdapter.startDiscovery();
	}
	
	public void startGameButtonHandler (View view) {
	     log("startGameButtonHandler");
	     showScreen(findOpponentScreen);
	}
	
	public void showInventoryButtonHandler (View view) {
		showScreen(inventoryScreen);
	}
	
	public void exitAppButtonHandler (View view) {
	     finish();
	}
	
	public void backToWelcomeScreenButtonHandler (View view) {
	     showScreen(welcomeScreen);
	}
	
	public void readyToBattleButtonHandler (View view) {
		int count = runesInBattle.getCount();
		ByteBuffer bb = ByteBuffer.allocate(count + 5);
		bb.putShort((short) (count + 3));
		bb.putShort(MSG_LOOK_AT_MY_RUNES);
		bb.put((byte) count);
		for (int i = 0; i < count; ++i) {
			bb.put(stiRunes.get(runesInBattle.getItem(i)).byteValue());
		}
		connectedThread.write(bb.array());
		readyToBattleButton.setEnabled(false);
	}
	
	public void acceptRunesButtonHandler (View view) {
		ByteBuffer bb = ByteBuffer.allocate(4);
		bb.putShort((short) 2);
		bb.putShort(MSG_ACCEPT_OPPONENT_RUNES);
		connectedThread.write(bb.array());
		opponentReadyText.setText("opp is READY");
		isOppReady = true;
		if (isMeReady)
			startBattle();
		else
			showScreen(chooseRunesScreen);
	}
	
	public void declineRunesButtonHandler (View view) {
		ByteBuffer bb = ByteBuffer.allocate(4);
		bb.putShort((short) 2);
		bb.putShort(MSG_DECLINE_OPPONENT_RUNES);
		connectedThread.write(bb.array());
		opponentReadyText.setText("opp is NOT READY");
		isOppReady = false;
		showScreen(chooseRunesScreen);
	}
	
	public void castButtonHandler (View view) {
		
		
		if (choosenRuneId > 0) {
			/// TODO: Анализ картинки
			Resources r = getResources();
			Bitmap img1 = BitmapFactory.decodeResource(r,
                    itbRunes.get(choosenRuneId));
			
			Bitmap img2 = canvas.getThread().getBitmap();
			
			double res = compareTwoPic.differencesTwoPic(img1, img2);
			
			blog("Compare result: " + String.valueOf(res));
			
			// Этот множитель будет определяться из точности рисунка
			// и еще чего-нибудь.
			float multiplier = (float)res;
			short runeId = (short) choosenRuneId;
			
			
			ByteBuffer bb = ByteBuffer.allocate(10);
			bb.putShort((short) 8);
			bb.putShort(MSG_CAST_SPELL);
			bb.putShort(runeId);
			bb.putFloat(multiplier);
			connectedThread.write(bb.array());
			
		}
		canvas.getThread().clearBitmap();
	}
	
	public void againYesButtonHandler (View view) {
		clearStateAfterBattle();
		showScreen(chooseRunesScreen);
	}
	
	public void againNoButtonHandler (View view) {
		clearStateAfterBattle();
		sendGoodbye();
		connectionEstablished = false;
		if (connectedThread != null) {
			connectedThread.cancel();
			connectedThread = null;
		}
		startAcceptThread();
		// Показываем стартовый экран
		showScreen(welcomeScreen);
	}
	
	private void sendGoodbye () {
		ByteBuffer bb = ByteBuffer.allocate(4);
		bb.putShort((short) 2);
		bb.putShort(MSG_GOODBYE);
		connectedThread.write(bb.array());
    }
	
	
	// Вот эта штука получает сообщения после запуска процедуры обнаружения
	// близлежащих устройств блютуз. Одно сообщение - одно устройство.
	// мы сохраняем этот список и выводим его в ListView.
	// В документации сказано, что поиск устройств занимает около 12 секунд.
	// Перед установлением соединения эту процедуру рекомендуется остановить,
	// так как она кушает много ресурсов.
	private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
	    public void onReceive(Context context, Intent intent) {
	        String action = intent.getAction();
	        // When discovery finds a device
	        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
	            // Get the BluetoothDevice object from the Intent
	            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
	            if (opponentsMACs.add(device.getAddress())) {
	            	// Add the name and address to an array adapter to show in a ListView
	            	opponentsStringArrayAdapter.add(device.getName() + "\n" + device.getAddress());
	            	opponentsArrayAdapter.add(device);
		    	}
	        }
	    }
	};
	
	// Инициализируем все, что относится к отображению рун.
	// Различные списки и все такое.
	private void initRunes () {
		inflateRunesIntoHashes();
		
		runesInInventory = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
		runesInBattle = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
		
		for (String s : runes)
			runesInInventory.add(s);
		
		inventoryList.setAdapter(runesInInventory);
		chooseRunesInvList.setAdapter(runesInInventory);
		chooseRunesInvList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
		      @Override
		      public void onItemClick(AdapterView<?> parent, final View view,
		          int position, long id) {
		    	String rune = runesInInventory.getItem(position);
		        if (runesInBattle.getCount() < maxRunesInBattle && !stringInArrayAdapter(runesInBattle, rune)) {
		        	runesInBattle.add(rune);
		        }
		      }
		    });
		chooseRunesBatList.setAdapter(runesInBattle);
		chooseRunesBatList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
		      @Override
		      public void onItemClick(AdapterView<?> parent, final View view,
		          int position, long id) {
	        	String rune = runesInBattle.getItem(position);
	        	runesInBattle.remove(rune);
		      }
		    });
		
	}
	
	// TODO: Inflate runes into hashes
	private void inflateRunesIntoHashes () {
		Resources r = getResources();
		
		itsRunes.put(RUNE_ANSUZ, r.getString(R.string.rune_ansuz));
		itsRunes.put(RUNE_BERKANO, r.getString(R.string.rune_berkano));
		itsRunes.put(RUNE_DAGAZ, r.getString(R.string.rune_dagaz));
		itsRunes.put(RUNE_ELHAZ, r.getString(R.string.rune_elhaz));
		itsRunes.put(RUNE_EVAZ, r.getString(R.string.rune_evaz));
		itsRunes.put(RUNE_EYVAZ, r.getString(R.string.rune_eyvaz));
		
		itbRunes.put(RUNE_ANSUZ, R.drawable.r_ansuz);
		itbRunes.put(RUNE_BERKANO, R.drawable.r_berkano);
		itbRunes.put(RUNE_DAGAZ, R.drawable.r_dagaz);
		itbRunes.put(RUNE_ELHAZ, R.drawable.r_elhaz);
		itbRunes.put(RUNE_EVAZ, R.drawable.r_evaz);
		itbRunes.put(RUNE_EYVAZ, R.drawable.r_eyvaz);
		
		stiRunes.put(r.getString(R.string.rune_ansuz), RUNE_ANSUZ);
		stiRunes.put(r.getString(R.string.rune_berkano), RUNE_BERKANO);
		stiRunes.put(r.getString(R.string.rune_dagaz), RUNE_DAGAZ);
		stiRunes.put(r.getString(R.string.rune_elhaz), RUNE_ELHAZ);
		stiRunes.put(r.getString(R.string.rune_evaz), RUNE_EVAZ);
		stiRunes.put(r.getString(R.string.rune_eyvaz), RUNE_EYVAZ);
		
		runes.add(r.getString(R.string.rune_ansuz));
		runes.add(r.getString(R.string.rune_berkano));
		runes.add(r.getString(R.string.rune_dagaz));
		runes.add(r.getString(R.string.rune_elhaz));
		runes.add(r.getString(R.string.rune_evaz));
		runes.add(r.getString(R.string.rune_eyvaz));
		
		/*
		 = 4;
		 = 5;
		RUNE_EYVAZ = 6;
		RUNE_FEHU = 7;
		RUNE_GEBO = 8;
		RUNE_HAGALAZ = 9;
		RUNE_INGVAZ = 10;
		RUNE_ISE = 11;
		RUNE_JERA = 12;
		RUNE_KENAZ = 13;
		RUNE_LAGUZ = 14;
		RUNE_MANAZ = 15;
		RUNE_NAUTIZ = 16;
		RUNE_OFALA = 17;
		RUNE_PERTRO = 18;
		RUNE_RAIDO = 19;
		RUNE_SOVILO = 20;
		RUNE_TIVAZ = 21;
		RUNE_TURISAZ = 22;
		RUNE_URUZ = 23;
		RUNE_VUNJO = 24
		*/
	}
	
	// Функция поиска строки в ArrayAdapter<String>
	private boolean stringInArrayAdapter(ArrayAdapter<String> arr, String str) {
		int len = arr.getCount();
		for (int i = 0; i < len; ++i) {
			if (str == arr.getItem(i))
				return true;
		}
		return false;
	}

	/**=========================================================================
	 * Вот с этой функции начинается выполнение приложения.
	 * Вообще во всех функциях, описанных на странице про жизненный цикл
	 * приложения я поставил вывод в лог имён этих функций.
	 * Чтобы следить за циклом. Потом уберем конечно.
	 *==========================================================================
	 */
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		gestureDetector = new GestureDetector(this, new GestureListener());
		
		// Создаем ссылки на элементы управления.
		initViewsLinks();
		
		// Скрываем все экраны
		hideAllScreens();
		
		// Прячем заголовок с названием приложения.
		// Может его можно и совсем убрать как-нибудь, не знаю. Такой шаблон.
		ActionBar actionBar = getActionBar();
		actionBar.hide();
		
		// Hide Find MOAR! button (discover other bluetooths)
		// Ну понятно, сначала должна быть видна только одна кнопка,
		// нажатие на которую выводит список сопряженных устройств.
		// (в функции findOpponentButtonHandler)
		findMoreOpponentsButton.setVisibility(View.INVISIBLE);
		
		// Инициализируем ссылку на текстовое поле лога и делаем его
		// прокручиваемым.
		logTextView.setMovementMethod(new ScrollingMovementMethod());
		battleLog.setMovementMethod(new ScrollingMovementMethod());
		// Выводим первое сообщение в лог.
		log(getResources().getString(R.string.onCreate));
		
		// Инициализируем блютуз адаптер. Если устройство не поддерживает
		// блютуз, сообщаем об этом пользователю и завершаем работу.
		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		if (mBluetoothAdapter == null) {
		    // Device does not support Bluetooth
			AlertDialog.Builder dlg = new AlertDialog.Builder(this);
			dlg.setTitle("No Bluetooth");
			dlg.setMessage("Your device does not support bluetooth.");
			dlg.setCancelable(false);
			dlg.setPositiveButton("Okay", new AlertDialog.OnClickListener() {
	            public void onClick(DialogInterface dialog, int which) {
	                dialog.dismiss();
	                System.exit(0);
	            }});
			dlg.create();
			dlg.show();
			return;
		}
		
		// Инициализация массивов блютуз устройств.
		opponentsStringArrayAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
		opponentsArrayAdapter = new ArrayAdapter<BluetoothDevice>(this, android.R.layout.simple_list_item_1);
		// И других массивов
		initRunes();
		
		// Вот собственно регистрируемся на прием сообщений о найденных
		// поблизости устройствах блютуз.
		IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
		registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
		
		// Инициализация opponentsList.
		// В этот список будут выводится имена найденных устройств.
		// Делаем так, чтобы по клику начиналась процедура установки соединения.
		opponentsList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
		      @Override
		      public void onItemClick(AdapterView<?> parent, final View view,
		          int position, long id) {
		        BluetoothDevice device = opponentsArrayAdapter.getItem(position);
		        tryConnectToOtherDevice(device);
		      }
		    });
		
		// Наш локальных блютуз должен быть включен и виден для других
		// устройств. Если это не так, выдаем пользователю запрос на включение.
		// Если же эти условия выполнены, то сразу запускаем прослушивающих
		// поток (серверный сокет).
		if (!mBluetoothAdapter.isEnabled() ||
			 mBluetoothAdapter.getScanMode() !=
			 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
			Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
			discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
			startActivityForResult(discoverableIntent, REQUEST_ENABLE_BT);
		}
		else {
			startAcceptThread();
		}
		
		// Показываем стартовый экран
		// TODO: show first screen
		//inflateRunesToBattle();
		showScreen(welcomeScreen);
	}
	
	// В этой функции обрабатываем результат запроса на включение блютуза.
	// Если пользователь отказался включить блютуз, закрываем лавочку,
	// иначе, запускаем серверный сокет.
	@Override
	protected void onActivityResult (int requestCode, int resultCode, Intent data) {
		if (requestCode == REQUEST_ENABLE_BT) {
			if (resultCode == RESULT_CANCELED) {
				finish();
			}
			else {
				startAcceptThread();
			}
		}
	}
	
	// Ясно, запуска серверного потока.
	private void startAcceptThread() {
		acceptThread = new AcceptThread();
		acceptThread.start();
	}
	
	// Эта функция вызывается после клика по имени какого-нибудь устройства.
	// Перестаем быть сервером, начинаем подключение к выбранному устройству.
	// На нём, разумеется, должен быть запущен экземпляр такого же приложения
	// в состоянии ожидания подключения.
	private void tryConnectToOtherDevice(BluetoothDevice device) {
		if (acceptThread != null) {
			acceptThread.cancel();
		}
		connectThread = new ConnectThread(device);
		connectThread.start();
	}
	
	// Точно не знаю зачем здесь ключевое слово synchronized, может оно
	// и не нужно. Появилось здесь, когда я пытался из сокетных потоков
	// влиять на UI. Ну и конечно все падало. Потом-то я прикрутил сообщения.
	
	// А функция просто выводит сообщение. 
	public void alert (String msg) {
		AlertDialog.Builder dlg = new AlertDialog.Builder(this);
		dlg.setTitle("Alert");
		dlg.setMessage(msg);
		dlg.create();
		dlg.show();
	}
	
	// Лог с автоматической прокруткой.
	public void log (String msg) {
		if (logTextView != null) {
			logTextView.append(msg + "\n");
			// find the amount we need to scroll.  This works by
		    // asking the TextView's internal layout for the position
		    // of the final line and then subtracting the TextView's height
			int scrollAmount = 0;
			Layout l = logTextView.getLayout();
			if (l != null) {
				scrollAmount = l.getLineTop(logTextView.getLineCount()) - logTextView.getHeight();
			}
		    // if there is no need to scroll, scrollAmount will be <=0
		    if (scrollAmount > 0)
		    	logTextView.scrollTo(0, scrollAmount);
		    else
		    	logTextView.scrollTo(0, 0);
		}
	}
	
	// То же самое только вывод идет в лог боя.
	public void blog (String msg) {
		if (battleLog != null) {
			battleLog.append(msg + "\n");
			int scrollAmount = 0;
			Layout l = battleLog.getLayout();
			if (l != null) {
				scrollAmount = l.getLineTop(battleLog.getLineCount()) - battleLog.getHeight();
			}
		    if (scrollAmount > 0)
		    	battleLog.scrollTo(0, scrollAmount);
		    else
		    	battleLog.scrollTo(0, 0);
		}
	}
	
	
	
	//==========================================================================
	//
	// Остальные функции жизненного цикла приложения
	//
	//==========================================================================
	@Override
	protected void onStart() {
		super.onStart();
		log(getResources().getString(R.string.onStart));
	}
	
	@Override
	protected void onResume() {
		super.onResume();
		log(getResources().getString(R.string.onResume));
	}
	
	@Override
	protected void onPause() {
		super.onPause();
		log(getResources().getString(R.string.onPause));
	}
	
	@Override
	protected void onSaveInstanceState (Bundle outState) {
		super.onSaveInstanceState(outState);
		log(getResources().getString(R.string.onSaveInstanceState));
	}
	
	@Override
	protected void onStop() {
		super.onStop();
		// Здесь подчищаем потоки.
		log(getResources().getString(R.string.onStop));
		if (connectThread != null) {
			connectThread.cancel();
			connectThread = null;
		}
		if (acceptThread != null) {
			acceptThread.cancel();
			acceptThread = null;
		}
		if (connectedThread != null) {
			connectedThread.cancel();
			connectedThread = null;
		}
		timerHandler.removeCallbacks(timerRunnable);
	}
	
	@Override
	protected void onRestart() {
		super.onRestart();
		log(getResources().getString(R.string.onRestart));
	}
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		log(getResources().getString(R.string.onDestroy));
		unregisterReceiver(mReceiver);
	}
	//==========================================================================
	// Конец жизненных функций (sad but true)
	//==========================================================================
	
	// Так еще немного осталось описать.
	// Это класс потока серверного сокета.
	private class AcceptThread extends Thread {
	    private final BluetoothServerSocket mmServerSocket;
	 
	    // Здесь сокет создается.
	    public AcceptThread() {
	    	// Вот так в главный поток отсылается какой-либо сообщение.
	    	mHandler.obtainMessage(WRITE_LOG_MSG, "AcceptThread starts").sendToTarget();
	    	// Use a temporary object that is later assigned to mmServerSocket,
	        // because mmServerSocket is final
	        BluetoothServerSocket tmp = null;
	        try {
	            // MY_UUID is the app's UUID string, also used by the client code
	            tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(
	            		MainActivity.APP_NAME, MainActivity.APP_UUID);
	        } catch (IOException e) {
	        	mHandler.obtainMessage(WRITE_LOG_MSG, e.toString()).sendToTarget();
	        }
	        mmServerSocket = tmp;
	    }
	 
	    // Здесь сокет ставится на прослушку.
	    public void run() {
	        BluetoothSocket socket = null;
	        // Keep listening until exception occurs or a socket is returned
	        while (true) {
	            try {
	            	mHandler.obtainMessage(WRITE_LOG_MSG, "before mmServerSocket.accept()").sendToTarget();
	                socket = mmServerSocket.accept();
	            } catch (IOException e) {
	            	// Если этот поток отменить вызовом cancel, то функция
	            	// accept бросит исключение, которое мы здесь перехватим,
	            	// и завершим все тихо-мирно.
	                break;
	            }
	            // If a connection was accepted
	            if (socket != null) {
	                // Do work to manage the connection (in a separate thread)
	            	// Тут надо полученный сокет передавать потоку, отвечающему
	            	// за передачу данных (ConnectedThread). Это мы и делаем
	            	// посылая сообщение SOCKET_CONNECTED в главный поток.
	            	// А серверный сокет мы закрываем, потому что он нам больше
	            	// не нужен (по крайней мере для этого соединения).
	            	try {
	            		mHandler.obtainMessage(SOCKET_CONNECTED, IS_SERVER, -1, socket).sendToTarget();
						mmServerSocket.close();
					} catch (Exception e) {
						mHandler.obtainMessage(WRITE_LOG_MSG, e.toString()).sendToTarget();
					}
	                break;
	            }
	        }
	    }
	 
	    // Will cancel the listening socket, and cause the thread to finish
	    public void cancel() {
	    	mHandler.obtainMessage(WRITE_LOG_MSG, "AcceptThread canceled.").sendToTarget();
	        try {
	            mmServerSocket.close();
	        } catch (IOException e) { }
	    }
	}
	
	// Это поток, отвечающий за подключение с клиентской стороны.
	private class ConnectThread extends Thread {
	    public BluetoothSocket mmSocket;
	    public BluetoothDevice mmDevice;
	 
	    // Здесь нам передали удаленное устройство, к которому мы и будем
	    // подключаться. Мы на основе этого создаем клиентский сокет.
	    public ConnectThread(BluetoothDevice device) {
	        // Use a temporary object that is later assigned to mmSocket,
	        // because mmSocket is final
	        BluetoothSocket tmp = null;
	        mmDevice = device;
	 
	        // Get a BluetoothSocket to connect with the given BluetoothDevice
	        try {
	            // MY_UUID is the app's UUID string, also used by the server code
	            tmp = device.createRfcommSocketToServiceRecord(MainActivity.APP_UUID);
	        } catch (IOException e) {
	        	mHandler.obtainMessage(WRITE_LOG_MSG, e.toString()).sendToTarget();
	        }
	        mmSocket = tmp;
	    }
	 
	    // Первым делом отменяем процедуру поиска устройств, чтобы не жрала
	    // ресурсы. Потом собственно начинаем подключение. Если оно удачно,
	    // передаем сокет потоку, отвечающему за передачу данных.
	    public void run() {
	        // Cancel discovery because it will slow down the connection
	        mBluetoothAdapter.cancelDiscovery();
	        try {
	            // Connect the device through the socket. This will block
	            // until it succeeds or throws an exception
	            mmSocket.connect();
	            mHandler.obtainMessage(SOCKET_CONNECTED, IS_CLIENT, -1, mmSocket).sendToTarget();
	        } catch (IOException connectException) {
	            // Unable to connect; close the socket and get out
	        	mHandler.obtainMessage(WRITE_LOG_MSG, connectException.toString()).sendToTarget();
	            try {
	                mmSocket.close();
	            } catch (IOException closeException) {
	            	mHandler.obtainMessage(WRITE_LOG_MSG, closeException.toString()).sendToTarget();
	            }
	            return;
	        }
	    }
	 
	    // Will cancel an in-progress connection, and close the socket
	    public void cancel() {
	        try {
	            mmSocket.close();
	        } catch (IOException e) { }
	    }
	}

	// Вот этот поток отвечает за передачу данных.
	// Я в нем еще не разобрался, а просто скопировал из примера.
	// Как разберусь, напишу более внятный комментарий.
	private class ConnectedThread extends Thread {
	    private final BluetoothSocket mmSocket;
	    private final InputStream mmInStream;
	    private final OutputStream mmOutStream;
	 
	    public ConnectedThread(BluetoothSocket socket) {
	        mmSocket = socket;
	        InputStream tmpIn = null;
	        OutputStream tmpOut = null;
	 
	        // Get the input and output streams, using temp objects because
	        // member streams are final
	        try {
	            tmpIn = socket.getInputStream();
	            tmpOut = socket.getOutputStream();
	        } catch (IOException e) {
	        	mHandler.obtainMessage(WRITE_LOG_MSG, e.toString()).sendToTarget();
	        }
	 
	        mmInStream = tmpIn;
	        mmOutStream = tmpOut;
	        mHandler.obtainMessage(WRITE_LOG_MSG, "ConnectedThread created").sendToTarget();
	    }
	 
	    public void run() {
	        byte[] buffer = new byte[RECEIVE_BUFFER_SIZE];  // buffer store for the stream
	        int bytes; // bytes returned from read()
	 
	        // Keep listening to the InputStream until an exception occurs
	        while (true) {
	            try {
	                // Read from the InputStream
	                bytes = mmInStream.read(buffer);
	                // Send the obtained bytes to the UI activity
	                mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer).sendToTarget();
	            } catch (IOException e) {
	            	mHandler.obtainMessage(WRITE_LOG_MSG, "ERROR: " + e.toString() +
	            			"\n>> " + e.getMessage()).sendToTarget();
	            	mHandler.obtainMessage(SOCKET_CLOSED).sendToTarget();
	                break;
	            }
	        }
	    }
	 
	    /* Call this from the main activity to send data to the remote device */
	    public void write(byte[] bytes) {
	        try {
	            mmOutStream.write(bytes);
	            mmOutStream.flush();
	        } catch (IOException e) { }
	    }
	 
	    /* Call this from the main activity to shutdown the connection */
	    public void cancel() {
	        try {
	            mmSocket.close();
	        } catch (IOException e) { }
	    }
	}

}
